-module(bbox).
-export([hittest/2]).
-export([new/0, build/1]).
-export([get_min/1, set_min/2]).
-export([get_max/1, set_max/2]).
-export([get_faces/1, set_faces/2, add_face/2]).

-record(bbox, {
            min,
            max,
            faces=[]
       }).

hittest(Ray, Box) ->
    Tnear = -9999999999999999999999999,
    Tfar = 9999999999999999999999999,
    {Xl, Yl, Zl} = vector:split(Box#bbox.min),
    {Xh, Yh, Zh} = vector:split(Box#bbox.max),

    {Ro, Rd} = ray:split(Ray),

    {Xo, Yo, Zo} = vector:split(Ro),
    {Xd, Yd, Zd} = vector:split(Rd),

    XTest = hittest_plane({Tnear, Tfar}, Xl, Xh, Xo, Xd),
    YTest = hittest_plane(XTest, Yl, Yh, Yo, Yd),
    ZTest = hittest_plane(YTest, Zl, Zh, Zo, Zd),

    case ZTest of
        false -> false;
        _Other -> true
    end.

hittest_plane(false, _L, _H, _O, _D) -> false;
hittest_plane(_Distances, L, H, O, 0.0) when O < L, O > H -> false;
hittest_plane(Distances, _L, _H, _O, 0.0) -> Distances;
hittest_plane({Near, Far}, L, H, O, D) when D /= 0 ->
    InvD = 1/D,
    T1 = (L - O)*InvD,
    T2 = (H - O)*InvD,

    Tmin = erlang:min(T1, T2),
    Tmax = erlang:max(T1, T2),

    N = erlang:max(Tmin, Near),
    F = erlang:min(Tmax, Far),

    case (N =< F) andalso (F >= 0) of
        true -> {N, F};
        _Other -> false
    end.


new() -> #bbox{}.

build(Vertices) ->
    build(Vertices, {0, 0, 0}, {0, 0, 0}).

build([], {MinX, MinY, MinZ}, {MaxX, MaxY, MaxZ}) ->
    #bbox{min=vector:new(MinX, MinY, MinZ), max=vector:new(MaxX, MaxY, MaxZ)};
build([Vertex|Tail], {MinX, MinY, MinZ}, {MaxX, MaxY, MaxZ}) ->
    {X, Y, Z} = vector:split(Vertex),
    Min = {erlang:min(MinX, X), erlang:min(MinY, Y), erlang:min(MinZ, Z)},
    Max = {erlang:max(MaxX, X), erlang:max(MaxY, Y), erlang:max(MaxZ, Z)},
    build(Tail, Min, Max).

get_min(Box) -> Box#bbox.min.
set_min(Min, Box) -> Box#bbox{min=Min}.

get_max(Box) -> Box#bbox.max.
set_max(Max, Box) -> Box#bbox{max=Max}.

get_faces(Box) -> Box#bbox.faces.
set_faces(Faces, Box) -> Box#bbox{faces=Faces}.
add_face(Face, Box) -> Box#bbox{faces=[Face|Box#bbox.faces]}.